前幾天已經看過,Scheduler 會替 Pod 選擇 Worker Node,而 kubelet、Container Runtime、CNI 與 CSI 會在該 Node 上把它真正準備起來。不過到這裡還有一個容易混淆的地方,
我們常說「部署一個 container」,但 Kubernetes 實際管理與派送的最小單位是 Pod。
Pod 可以只有一個 container,也可以包含數個 container,彼此之間可能有依賴關係。今天主要針對 Pod 以及不同情境類型的 Container 來說明,Pod 裡有哪些 container、它們什麼時候啟動與結束,以及為什麼有時候明明是自己的應用沒改,Pod 卻會因為另一個 container 而無法啟動。
Pod 不等於 Container,而一個 Pod 內也不一定只有一個 Container
Kubernetes 將 Pod 定義為最小可部署單位,一個 Pod 會把一個或多個 container 放在同一個執行環境:它們會一起被排程到同一台 Worker Node、共享 Pod IP 與網路空間,也可以掛載同一個 Volume。

以本次微服務架構的後端站台為例,api-a 可以獨立部署成一個 Pod,Pod 內主要放執行 Kestrel 的 ASP.NET Core WebAPI container。而 api-b 不需要放進同一個 Pod,因為它是另一個可獨立部署、更新與擴展(Scaling)的服務,應該各自放在不同的 Pod,再透過 Service 以網路互相呼叫。
只有在多個 container 必須接續、同時工作時,才適合放進同一個 Pod。例如應用程式需要搭配 Log 搜集器/轉發器一起運作,或啟動前必須先完成某個一次性的初始化工作,這些 container 可以隨 Pod 一起被排程,並共享該 Pod 的網路環境(可以使用 localhost 去互相溝通);若掛載同一個 Volume,也能共享檔案。
同一 Pod 內的 container 至少共享以下幾件事:
localhost 與不同 Port Number 互相連線。但 container 並不會因此自動共享所有東西。例如每個 container 仍有自己的 image、process、environment variable、resource request/limit 與預設檔案系統。排障時也要看清楚是哪一個 container 失敗,而不是只看 Pod 整體狀態。
| 類型 | 何時運作 | 常見用途 |
|---|---|---|
| App Container | Pod 正常服務期間持續運作 | Web API、前端網站、背景處理程序 |
| Init Container | 啟動 App Container 前,依宣告順序逐一執行,處理完成後此容器將下架 | 等待必要條件、產生一次性檔案、資料初始化 |
| Sidecar Container | 與 App Container 協作,在 Pod 存活期間提供輔助功能;實際啟動與停止順序取決於採用傳統或 native 配置 | Proxy、log 收集、監控、同步檔案 |
例如 ASP.NET Core API、Nginx 或背景 Worker。它才是處理 request、執行商業邏輯、回傳資料的主要容器。
一個 Pod 雖然可以有多個 App Container,但如果它們沒有明確的共用資源或需要有相同生命週期的需求,基本上拆成不同 Pod 通常更容易去達到 Auto Scaling、debug 與 release。
Init Container 依 Deployment 的 .spec.template.spec.initContainers 宣告順序執行,而且前一個 Init Container 成功結束後,才會啟動下一個。所有 Init Container 都成功後,kubelet 才會開始啟動 App Container。
例如,一個 Init Container 可以確認某個必要檔案存在、把範本設定檔轉成應用實際需要的設定檔,或等待外部依賴服務是可連線的狀態。
它不適合承接長時間服務 request,如果它一直無法成功結束,Pod 就會停在初始化(init)階段。
Sidecar 是一種「協作角色」,它的工作是補足 App Container 的能力,或讓 App Container 的職責更單純,例如:
Sidecar 是協作角色,常見有兩種配置方式,分別是傳統 Sidecar 與 native Sidecar。
為了和 Kubernetes 的 native Sidecar 區分(也就是用 Init Container 作為 Sidecar 的方式),我將放在
containers的 Sidecar 稱為「傳統 Sidecar」。
傳統 Sidecar 寫在 .spec.template.spec.containers,與 App Container 同層。
從 Kubernetes API 的角度來看,兩者都是一般的 App container。
像是 Istio injection 所加入的 istio-proxy,就是常見例子。所有 Init Container 成功後,App Container 與傳統 Sidecar 才會開始,但 Kubernetes 預設不保證它們彼此的啟動或停止先後順序。
其實我也是撰寫這篇時才發現原來也有這種方式可以達成 Sidecar。
Kubernetes 在 v1.32 版本提供了這種方式來讓 Init Container 也能當作 Sidecar 來用。它寫在 .spec.template.spec.initContainers,並在該 container 設定 restartPolicy: Always,這種使用方式不會像一般 Init Container 一樣完成後結束。
native Sidecar 會依 initContainers 的宣告順序先啟動,並持續運作到 Pod 終止;Kubernetes 會在主要 App Container 都停止後,才關閉 native Sidecar。
以下以 Deployment 的 Pod template 為例,只保留與兩種 Sidecar 配置有關的區塊。兩者最大的差異,就是 Sidecar 所在的欄位與 native Sidecar 額外設定的 restartPolicy: Always。
傳統 Sidecar:兩個 container 都放在 containers
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: api
image: example/api:1.0
- name: proxy
image: example/proxy:1.0
native Sidecar:Sidecar 放在 initContainers,但會持續執行
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
initContainers:
- name: proxy
image: example/proxy:1.0
restartPolicy: Always
containers:
- name: api
image: example/api:1.0
以下比較傳統 Sidecar 與 native Sidecar 的 lifecycle:

把 Day 6、Day 7 的流程與今天的 container 角色接起來,可以得到下列流程:

在實際操作 K8s 時,可以透過 kubectl get pod 與 kubectl describe pod 來觀察 Pod 的狀態,而這兩種指令顯示的狀態內容會有以下三種內容:
| 層次 | 狀態 | 用意 |
|---|---|---|
| Pod phase | Pending、Running、Succeeded、Failed、Unknown |
Pod 整體生命週期的摘要狀態。Running 不代表應用已經 Ready。 |
| container state | Waiting、Running、Terminated |
Pod 裡每一個 App Container、Init Container 或 Sidecar 各自的執行狀態。 |
kubectl 的 STATUS 顯示 |
Init:ImagePullBackOff、CrashLoopBackOff、Terminating |
為了方便人閱讀而彙整出的提示;不能直接當成 Pod phase。 |
例如 phase: Running 只表示 Pod 已被指派到 Node、container 已建立,且至少有一個 container 正在執行或重啟中;它不代表所有 container 都正常,也不代表 Pod 已經 Ready、可以接收流量。
Init:ImagePullBackOff 表示 initContainers 中某個 container 的 image 無法拉取。
CrashLoopBackOff 則表示某個 container 反覆失敗、重啟等待時間逐漸拉長。兩者都是排查的重要線索,但不是 Pod phase。
因此看到 Pod 異常時,除了 kubectl get pod,也應使用 kubectl describe pod <pod-name> 查看 Event,並確認 initContainerStatuses 與 containerStatuses 中究竟是哪一個 container 出錯。
分享個真實經驗,曾經有一組 Pod 顯示 Init:ImagePullBackOff,一開始直覺會以為是 AP 程式的 image、Pod 網路,甚至 CoreDNS 出了問題,但詳細看 Event 與 initContainerStatuses 後,才發現失敗的是 Service Mesh 自動注入的 container image,而不是 AP 程式的 image。
由於 Pod 顯示的是 Init:ImagePullBackOff,實際排查時還要確認失敗的 container 名稱,以及它位於 initContainers 還是 containers。
更特別的是,只有一台 Worker Node 會失敗。該 Node 的 Runtime 要拉取新 image 時,使用的 Node DNS resolver 發生 timeout,而 AP 程式 image 因為已經存在 local cache,所以看起來像「應用正常,只有奇怪的 container 壞掉」。
這個案例提醒我:看到 Pod 啟動失敗時,先用 kubectl describe pod 看 Event 與每個 container 的狀態,確認到底是 App Container、Init Container、Sidecar,還是 Node 層的 Runtime 問題。
在 Pod 擁有多個 container 的情況下,每個 container 都可能出現問題。
Pod 是 Kubernetes 真正部署的最小單位,它把一個或多個需要緊密合作的 container 放在同一個環境中。
Init Container 負責啟動前的工作,App Container 提供服務,Sidecar 則在旁協助運作。
Sidecar 可以是與 App Container 同層的傳統 Sidecar,也可以是具有順序的 native Sidecar。